# coding=utf-8 ################################################################
import struct
import hashlib
import time
import database
import utils

class SocketWrapper:
    def __init__(self, s):
        self.s = s
    
    def sendall(self, b):
        self.s.sendall(b)
    
    def recvall(self, bytes):
        data = b''
        while len(data) != bytes:
            r = self.s.recv(bytes-len(data))
            if len(r) == 0:
                raise utils.ValidateDataError("Data ended unexpectedly") 
            data += r
        return data

def do_exchange_server(s, ip):
    db = database.get()
    s = SocketWrapper(s)
    
    # INSERT peer and get id
    port, = struct.unpack("H", s.recvall( 2 ) )
    utils.validate_port(port)
    db.execute("BEGIN IMMEDIATE")
    peer_in_db, = db.execute(
        "SELECT COUNT(*) FROM peers WHERE "+
        "ip=? AND port=?", (ip, port)).fetchone()
    if peer_in_db == 0:
        db.execute("INSERT INTO peers (ip, port, last_seen) VALUES "+
                   "(?, ?, ?)", (ip, port, -1))
        peerid, = db.execute("SELECT last_insert_rowid()").fetchone()
    else:
        peerid, = db.execute("SELECT id FROM peers WHERE ip=? AND port=?", 
                (ip, port) ).fetchone()
    db.commit()

    entrylist = recv_entrylist(s)
    send_entrylist(s, db)

    recv_peers(s, db)
    send_peers(s, db)

    recv_entries(s, db)
    send_entries(s, db, entrylist)
    
    recv_votes(s, db, peerid)
    send_votes(s, db)
    
    db.close()

def do_exchange_client(s, peerid):
    db = database.get()
    s = SocketWrapper(s)
    
    # send port
    my_port, = db.execute("SELECT value FROM misc WHERE name='port'").fetchone()
    s.sendall( struct.pack("H", int(my_port)) )
    
    send_entrylist(s, db)
    entrylist = recv_entrylist(s)
    
    send_peers(s, db)
    recv_peers(s, db)
    
    send_entries(s, db, entrylist)
    recv_entries(s, db)
    
    send_votes(s, db)
    recv_votes(s, db, peerid)
    
    db.close()

def send_entrylist(s, db):
    entry_hashes = db.execute("SELECT hash FROM entries").fetchall()
    s.sendall(struct.pack("I", len(entry_hashes)))
    for entry_hash, in entry_hashes:
        s.sendall(entry_hash)
    
def recv_entrylist(s):
    num_entries, = struct.unpack("I", s.recvall(4))
    entrylist = []
    for n in range(num_entries):
        entrylist.append( s.recvall(20) )
    return entrylist
    
def send_peers(s, db):
    peers = db.execute("SELECT ip, port FROM peers "+
                            "WHERE last_seen != -1").fetchall()
    s.sendall(struct.pack("I", len(peers)))
    for ip, port in peers:
        s.sendall( ip+struct.pack("H", port) )
        
def recv_peers(s, db):
    num_pears, = struct.unpack("I", s.recvall(4))
    for n in range(num_pears):
        ip = s.recvall(4)
        utils.validate_ip(ip)
        port, = struct.unpack("H", s.recvall(2))
        utils.validate_port(port)
        db.execute("INSERT OR IGNORE INTO peers (ip, port, last_seen) VALUES (?, ?, ?)",
                   (ip, port, -1))

def send_entries(s, db, entrylist):
    entries = db.execute("SELECT data FROM entries WHERE NOT hash IN ("+
                             ",".join('?'*len(entrylist))+")", 
                             entrylist).fetchall()
    s.sendall(struct.pack("I", len(entries)))
    for data, in entries:
        s.sendall(struct.pack("I", len(data)))
        s.sendall(data)

def recv_entries(s, db):
    num_entries, = struct.unpack("I", s.recvall(4))
    for n in range(num_entries):
        data_len, = struct.unpack("I", s.recvall(4))        
        data = s.recvall(data_len)
        utils.insert_entry(db, data)
        
def send_votes(s, db):
    votes = db.execute("SELECT entries.hash, my_votes.value, my_votes.time "+
                       "FROM my_votes INNER JOIN entries ON "+
                       "entries.id=my_votes.entry").fetchall()
    s.sendall(struct.pack("I", len(votes)))
    for hash, value, vote_time in votes:
        s.sendall(hash)
        s.sendall(struct.pack("i", value))
        s.sendall(struct.pack("q", vote_time))

def recv_votes(s, db, peerid):
    # Get votes
    num_votes, = struct.unpack("I", s.recvall(4))
    votes = []
    votes_norm = 0.0
    for n in range(num_votes):
        hash = s.recvall(20)
        value, = struct.unpack("i", s.recvall(4))
        vote_time, = struct.unpack("q", s.recvall(8))
        utils.validate_time(vote_time)
        votes.append( {"entry": hash, "value": value, "time": vote_time} )
        votes_norm += abs(value)
    
    # Normalize votes
    if votes_norm == 0:
        return
    for vote in votes:
        vote["value"] /= votes_norm
    
    # insert vote
    utils.insert_votes(db, peerid, votes)    
